<template>
  <div class="pro-table">
    <SearchForm
      class="card form-main"
      v-bind="searchConfig"
      :form-list="filterColumns"
      :model="filterState.searchInfo"
      :enum-map="enumMap"
      :onSearch="handleSearch"
      :onReset="handleReset"
    >
    <template v-for="slot in Object.keys($slots)" #[slot]="scope">
      <slot :name="slot" v-bind="scope"></slot>
    </template>
    </SearchForm>

    <div class="card table-main">
      <!-- 表格头部 操作按钮 -->
      <div class="table-header" v-if="showToolbar && !mode">
        <div class="header-button-left">
          <slot name="tableHeader" :selectedIds="selectedIds" :selectedList="selectedList"></slot>
        </div>
        <div class="header-button-right">
          <slot name="toolButton">
            <Toolbar v-bind="toolbarConfig" @refresh="refresh" @setting="openColSetting" />
          </slot>
        </div>
      </div>
      <!-- 表格主体 -->
      <el-table
        ref="tableRef"
        v-loading="loading"
        v-bind="$attrs"
        :data="tableData"
        :border="border"
        :row-key="rowKey"
        @selection-change="handleSelectionChange"
      >
        <template v-for="item in tableColumns">
          <el-table-column
            :key="item.prop ?? item.type"
            v-bind="item"
            :align="item.align ?? 'center'"
            :width="item.width ?? '55'"
            v-if="item.type == 'radio'"
          >
            <template #default="{ row }">
              <el-radio-group class="ml-4" v-model="singleSelectedId" @change="() => handleSelectionChange([row])">
                <el-radio :label="row[rowKey]" size="small">
                  <span></span>
                </el-radio>
              </el-radio-group>
            </template>
          </el-table-column>
          <!-- selection || index -->
          <el-table-column
            :key="item.prop ?? item.type"
            v-bind="item"
            :align="item.align ?? 'center'"
            :width="item.width ?? '55'"
            :reserve-selection="item.type == 'selection'"
            v-if="item.type == 'selection' || item.type == 'index'"
          >
          </el-table-column>
          <!-- expand -->
          <el-table-column
            :key="item.prop ?? item.type"
            v-bind="item"
            :align="item.align ?? 'center'"
            :width="item.width ?? '80'"
            v-if="item.type == 'expand'"
            v-slot="scope"
          >
            <slot :name="item.type" v-bind="scope"></slot>
          </el-table-column>
          <el-table-column
            v-if="!item.type && item.prop && !item.hidden"
            v-bind="item"
            :align="item.align ?? 'center'"
            :key="item.prop"
            :column-key="item.prop"
            :prop="item.prop"
            :label="item.label"
            :width="item.width"
            :min-width="item.minWidth"
            :fixed="item.fixed"
            :header-align="item.headerAlign"
            :sortable="item.sortable"
            :show-overflow-tooltip="(item.showOverflowTooltip === false || item.prop === COLUMN_OPERATE_PROP) ? false : true"
          >
            <template #default="{ row, $index }">
              <slot :name="item.prop" :row="row" :index="$index" :col="item">
                <template v-if="item.prop === COLUMN_OPERATE_PROP">
                  <Button v-for="(btn, i) in getButtons(row, item)" :key="i" :btn="btn" :row="row" :index="$index" />
                </template>
                <span v-else>{{ renderCellData(item, row, $index) }}</span>
              </slot>
            </template>
          </el-table-column>
        </template>
        <!-- 插入表格最后一行之后的插槽 -->
        <template #append>
          <slot name="append"> </slot>
        </template>
        <!-- 表格无数据情况 -->
        <template #empty>
          <div class="table-empty">
            <slot name="empty">
              <!-- <img src="@/assets/images/notData.png" alt="notData" /> -->
              <div>暂无数据</div>
            </slot>
          </div>
        </template>
      </el-table>

      <!-- 分页组件 -->
      <Pagination
        v-if="pagination"
        :pageData="filterState.pagData"
        :handle-size-change="handleSizeChange"
        :handle-current-change="handleCurrentChange"
      />
    </div>

    <!-- 列设置 -->
    <ColumnSetting v-if="toolbarConfig.setCol" ref="colRef" v-model="tableColumns" />
  </div>
</template>

<script lang="ts">
export default {
  name: "ProTable",
  inheritAttrs: false,
}
</script>
<script setup lang="ts">
import { ColumnProps, ProTableProps } from '@/types/table';
import { ElTable } from "element-plus";
import Toolbar from './Toolbar.vue'
import ColumnSetting from './ColumnSetting.vue'
import Button from './Button.vue'
import Pagination from './Pagination.vue'
import { filterEnum, formatValue, getValidKey, COLUMN_OPERATE_PROP } from "../utils";

// 接受父组件参数，配置默认值
const props = withDefaults(defineProps<ProTableProps>(), {
  searchConfig: () => ({
    labelWidth: "80px",
  }),
  showToolbar: true,
  toolbarConfig: () => ({
  }),
  columns: () => [],
  requestAuto: true,
  pagination: true,
  params: {},
  border: true,
  rowKey: "id",
  mode: ""
});

const emit = defineEmits(["reset", "selection-change"]);

// 条件搜索列
const filterColumns = ref<any[]>([]);
function initFilterColumns() {
  const arr: any[] = props.columns
    .filter(o => o.search)
    .map((item, idx) => {
      const { label, prop, options, valueType } = item
      const search = typeof item.search === 'boolean' ? {
        valueType
      } : item.search
      const order = search?.order ?? idx;
      const newItem = {
        label,
        prop,
        ...search,
        order,
      }
      return options ? { options, ...newItem } : newItem
    })
    .sort((a, b) => a.order - b.order)
  filterColumns.value = arr
}
// 可视表格列
const tableColumns = ref<ColumnProps[]>([]);
function initTableColumns() {
  tableColumns.value = props.columns
    .filter(item => {
      if (["dialog-multiple", "dialog-single"].includes(props.mode)) {
        return !item.hideInTable && item.prop !== COLUMN_OPERATE_PROP
      }
      return !item.hideInTable
    })
    .map(item => {
      item.hidden = item.hidden ?? false;
      return item;
    })
}
// 初始化表格列
function initColumns() {
  initFilterColumns()
  initTableColumns()
}

// 列设置 ==> 过滤掉不需要设置的列
const colRef = ref();
const openColSetting = () => {
  colRef.value?.openColSetting();
}

// 字典数据
const enumMap = computed(() => {
  return props.columns.reduce((cur, next: ColumnProps) => {
    if (next.options && next.prop) {
      cur[next.prop!] = next.options
    }
    return cur
  }, {} as { [key: string]: any[] })
})

// 渲染表格数据
const renderCellData = (item: ColumnProps, row: { [key: string]: any }, index: number) => {
  if (typeof item.customRender === "function") {
    return item.customRender(row, index)
  }
  const cellValue = row[item.prop!];
  const list = item.options
  return list ? filterEnum(cellValue, list) : formatValue(cellValue);
};

// 获取操作列按钮
const getButtons = computed(() => {
  return (row: any, item: ColumnProps) => {
    if (!item.buttons) return []
    return item.buttons.filter((btn) => {
      return typeof btn.hidden === "function" ? !btn.hidden(row) : true;
    });
  };
});

// 列表数据源
const tableData = ref<any[]>([]);
function setTableData(arr: any[]) {
  tableData.value = arr;
}

// 加载状态
const loading = ref(false);
function setLoading(value: boolean) {
  loading.value = value;
}
// 查询条件
const filterState = reactive({
  searchInfo: {},
  pagData: {
    pageNum: 1,
    pageSize: 10,
    total: 0
  }
});
// 初始化查询条件
function initSearchInfo() {
  const obj: Record<string, any> = {};
  filterColumns.value.forEach(item => {
    const { prop, defaultValue = "" } = item;
    if (prop) {
      obj[prop] = defaultValue || "";
    }
  });
  filterState.searchInfo = obj;
}
function setPag(value: any) {
  filterState.pagData = {
    ...filterState.pagData,
    ...value
  };
}
// 初始化请求参数
function initRequestParam() {
  initSearchInfo();
  setPag(props.pagination);
}
// 获取请求参数
function getRequestParam() {
  const { searchInfo, pagData } = filterState
  const { pageNum, pageSize } = pagData;
  return {
    ...getValidKey(searchInfo),
    pageNum,
    pageSize,
    ...props.params
  };
}
// 查询表格
async function handleRequest() {
  if (typeof props.requestApi !== "function") return;
  setLoading(true);
  const reqParams = getRequestParam();
  const res = await props.requestApi(reqParams);
  if (res) {
    const { list = [], total } = res;
    setTableData(list);
    setPag({ total });
  }
  setLoading(false);
}

// 监听传入参数变化，重新获取表格数据
watch(() => props.params, handleRequest, { deep: true });

// 点击查询
function handleSearch() {
  setPag({ pageNum: 1 });
  handleRequest();
}
// 重置查询条件
function handleReset() {
  initSearchInfo();
  emit("reset");
  handleRequest();
}
function handleSizeChange(pageSize: number) {
  setPag({ pageNum: 1, pageSize });
  handleRequest();
}
function handleCurrentChange(pageNum: number) {
  setPag({ pageNum });
  handleRequest();
}

// 表格 DOM 元素
const tableRef = ref<InstanceType<typeof ElTable>>();

// 单选radio选中项
const singleSelectedId = ref<string | number | boolean>();

// 复选选中项
const selectedList = ref<Record<string, any>[]>([]);
const selectedIds = computed(() => {
  const { rowKey } = props;
  return selectedList.value.map(item => item[rowKey!])
});
// 选择事件
function handleSelectionChange(val: Record<string, any>[]) {
  selectedList.value = val;
  emit("selection-change", val);
}
// 切换选中行
const toggleRowSelection = () => {
  if (!Array.isArray(props.selectedRows) || !props.selectedRows.length) return
  if (props.mode === "dialog-single") {
    const selectedRow = props.selectedRows[0] as Record<string, any>
    singleSelectedId.value = selectedRow ? selectedRow[props.rowKey] : ""
    return
  }
  props.selectedRows.forEach((row: any) => {
    tableRef.value?.toggleRowSelection(row, true)
  })
};

// 获取el-table表格组件实例
function getTableInstance() {
  return tableRef.value;
}

// 刷新表格
function refresh() {
  handleRequest();
}
// 刷新表格,重置所有查询条件
function reload() {
  initRequestParam();
  handleRequest();
}

// 初始化请求
onMounted(() => {
  initColumns();
  initRequestParam();
  if (props.requestAuto) {
    handleRequest();
  }
  toggleRowSelection()
});

// 暴露给父组件的参数和方法(外部需要什么，都可以从这里暴露出去)
defineExpose({
  tableData,
  filterState,
  refresh,
  reload,
  getTableInstance,
});
</script>

<style lang="scss" scoped>
@import "../index.scss";
</style>
